/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.chinasoft_ohos.commontools.kt2j;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * A helper to covert Kotlin extensions to Java language.
 */
public final class KtExtensions {

    public interface UnitBlock<T> {
        /**
         * Call this block function.
         *
         * @param invoker The invoker call the function which own this interface callback.
         */
        void invoke(T invoker);
    }

    public interface ResultBlockInvoker<T, R> {
        /**
         * Call this block function.
         *
         * @param invoker The invoker call the function which own this interface callback.
         * @return The result of this block function.
         */
        R invoke(T invoker);
    }

    public interface ResultBlockNoInvoker<R> {
        /**
         * Call this block function.
         *
         * @return The result of this block function.
         */
        R invoke();
    }

    public interface PredicateBlock<T> {
        /**
         * Call this block function.
         *
         * @param invoker The invoker call the function which own this interface callback.
         * @return A rule to match the result.
         */
        boolean predicate(T invoker);
    }

    /**
     * Calls the specified function block and returns its result.
     *
     * @param block The block function.
     * @param <R> The type of result.
     * @return The result of this function.
     */
    public static <R> R run(ResultBlockNoInvoker<R> block) {
        return block.invoke();
    }

    /**
     * Calls the specified function block with this value as its receiver and returns its result.
     *
     * @param invoker The invoker of this block function.
     * @param block The block function.
     * @param <T>The type of invoker.
     * @param <R> The type of result.
     * @return The result of this function.
     */
    public static <T, R> R run(T invoker, ResultBlockInvoker<T, R> block) {
        return block.invoke(invoker);
    }

    /**
     * Calls the specified function block with the given receiver as its receiver and returns its result.
     * It is the same as {@link KtExtensions#run(Object, KtExtensions.ResultBlockInvoker)} in java language.
     *
     * @param invoker The invoker of this block function.
     * @param block The block function.
     * @param <T>The type of invoker.
     * @param <R> The type of result.
     * @return The result of this function.
     */
    public static <T, R> R with(T invoker, ResultBlockInvoker<T, R> block) {
        return block.invoke(invoker);
    }

    /**
     * Calls the specified function block with this value as its receiver and returns this value.
     *
     * @param invoker The invoker of this block function.
     * @param block The block function.
     * @param <T>The type of invoker.
     * @return The result of this function.
     */
    public static <T> T apply(T invoker, UnitBlock<T> block) {
        block.invoke(invoker);
        return invoker;
    }

    /**
     * Calls the specified function block with this value as its argument and returns this value.
     * It is the same as {@link KtExtensions#apply(Object, KtExtensions.UnitBlock)} in java language.
     *
     * @param invoker The invoker of this block function.
     * @param block The block function.
     * @param <T>The type of invoker.
     * @return The result of this function.
     */
    public static <T> T also(T invoker, UnitBlock<T> block) {
        block.invoke(invoker);
        return invoker;
    }

    /**
     * Calls the specified function block with this value as its argument and returns its result.
     * It is the same as {@link KtExtensions#run(Object, KtExtensions.ResultBlockInvoker)} in java language.
     *
     * @param invoker The invoker of this block function.
     * @param block The block function.
     * @param <T> The type of invoker.
     * @param <R> The type of result.
     * @return The result of this function.
     */
    public static <T, R> R let(T invoker, ResultBlockInvoker<T, R> block) {
        return block.invoke(invoker);
    }

    /**
     * Returns this value if it satisfies the given predicate or null, if it doesn't.
     *
     * @param invoker The invoker of this block function.
     * @param block A block function callback to invoke by the invoker.
     * @param <T> The type of invoker.
     * @return The invoker if match the result or null if not match.
     */
    public static <T> T takeIf(T invoker, PredicateBlock<T> block) {
        if (block.predicate(invoker)) {
            return invoker;
        } else {
            return null;
        }
    }

    /**
     * Returns this value if it does not satisfy the given predicate or null, if it does.
     *
     * @param invoker The invoker of this block function.
     * @param block A block function callback to invoke by the invoker.
     * @param <T> The type of invoker.
     * @return Null if match the result or the invoker if not match.
     */
    public static <T> T takeUnless(T invoker, PredicateBlock<T> block) {
        if (!block.predicate(invoker)) {
            return invoker;
        } else {
            return null;
        }
    }

    /**
     * Executes the given function action specified number of times.
     *
     * @param times The repeat count.
     * @param block The action to do.
     */
    public static void repeat(int times, UnitBlock<Integer> block) {
        for (int i = 0; i < times; i++) {
            block.invoke(i);
        }
    }

    /**
     * Simply to do repeat action for collection.
     *
     * @param collection The collection to foreach.
     * @param block The action to do.
     * @param <E> The type of element of collection.
     */
    public static <E> void forEach(Collection<E> collection, UnitBlock<E> block) {
        for (E element : collection) {
            block.invoke(element);
        }
    }

    /**
     * Returns a list containing the results of applying the given transform function
     * to each element in the original collection.
     *
     * @param collection Original collections to transform.
     * @param block The transform action to do.
     * @param <E> The type of element of original collection.
     * @param <R> The type of element of result collection.
     * @return The list after transform.
     */
    public static <E, R> List<R> map(Collection<E> collection, ResultBlockInvoker<E, R> block) {
        List<R> result = new ArrayList<>();
        for (E element : collection) {
            // do transform from E to R
            R r = block.invoke(element);
            result.add(r);
        }
        return result;
    }
}
